package tool.json

import kotlinx.serialization.json.*
import utils.snakeToCamel
import utils.snakeToLowerCamel


sealed interface ConvertType

data class Convert(
    val map: LinkedHashMap<ConvertClass, String> = LinkedHashMap()
) {
    lateinit var root: ConvertClass
}

class ArrayType(val type: ConvertType) : ConvertType

data object AnyType : ConvertType

data object StringType : ConvertType

data object BooleanType : ConvertType

data object LongType : ConvertType

data object DoubleType : ConvertType

data class ConvertClass(
    val attributes: Set<CovertAttribute>
) : ConvertType

data class CovertAttribute(
    val name: String,
    val type: ConvertType,
    var isNullable: Boolean,
)

const val JSON_TO_KOTLIN_TOP_CLASS = "Root"

fun jsonToKotlin(jsonRootElement: JsonElement): String {
    val convert = Convert()
    when (jsonRootElement) {
        is JsonObject -> {
            val type = covertJsonObject(jsonRootElement, convert)
            convert.root = type as ConvertClass
        }
        is JsonArray -> {
            val arrayType = covertJsonArray(jsonRootElement, "root", convert)
            val type = ConvertClass(attributes = setOf(CovertAttribute("data", arrayType, false)))
            convert.root = type
        }
        else -> {
            return "解析 json 失败"
        }
    }
    return convert.covertToKotlinClass()
}

private fun covertJsonObject(jsonObject: JsonObject, convert: Convert): ConvertType {
    val attributes = mutableSetOf<CovertAttribute>()
    for (entry in jsonObject) {
        val key = entry.key
        val jsonElement = entry.value
        val (type, isNullable) = covertJsonElement(jsonElement, key, convert)
        attributes.add(CovertAttribute(key, type, isNullable))
    }
    return ConvertClass(attributes.toSet())
}

private fun covertJsonElement(jsonElement: JsonElement, name: String, convert: Convert): Pair<ConvertType, Boolean> {
    var isNullable = false
    val type = when (jsonElement) {
        is JsonNull -> {
            isNullable = true
            AnyType
        }
        is JsonPrimitive -> {
            covertJsonPrimitive(jsonElement)
        }

        is JsonObject -> {
            val type = covertJsonObject(jsonElement, convert)
            if (!convert.map.containsKey(type)) {
                convert.map[type as ConvertClass] = name.snakeToCamel()
            }
            type
        }
        is JsonArray -> {
            covertJsonArray(jsonElement, name, convert)
        }
    }
    return type to isNullable
}

private fun covertJsonArray(jsonArray: JsonArray, name: String, convert: Convert): ArrayType {
    val types = mutableListOf<ConvertType>()
    for (element in jsonArray) {
        val (type, _) = covertJsonElement(element, name + "_item", convert)
        types.add(type)
    }
    // TODO merge types
    return ArrayType(types.firstOrNull() ?: AnyType)
}

private fun covertJsonPrimitive(jsonPrimitive: JsonPrimitive): ConvertType {
    if (jsonPrimitive.isString) {
        return StringType
    }
    if (jsonPrimitive.booleanOrNull != null) {
        return BooleanType
    }
    if (jsonPrimitive.longOrNull != null) {
        return LongType
    }
    if (jsonPrimitive.doubleOrNull != null) {
        return DoubleType
    }
    return AnyType
}


fun Convert.covertToKotlinClass(): String {
    val sb = StringBuilder()
    sb.appendLine("data class $JSON_TO_KOTLIN_TOP_CLASS(")
    var rootDefault = "field"
    var rootDefaultNum = 1
    root.attributes.forEach {
        val propertyName = it.name.snakeToLowerCamel().let { proName ->
            proName.ifEmpty {
                rootDefault.also { rootDefault += rootDefaultNum++ }
            }
        }
        sb.appendLine("    val ${propertyName}: ${getConvertTypeName(it.type)}${if (it.isNullable) "?" else ""}")
    }
    sb.appendLine(")")
    for (entry in map.entries.reversed()) {
        val type = entry.key
        val name = entry.value
        sb.appendLine()
        sb.appendLine("data class $name(")
        var default = "field"
        var defaultNum = 1
        type.attributes.forEach {
            val propertyName = it.name.snakeToLowerCamel().let { proName ->
                proName.ifEmpty {
                    default.also { default += defaultNum++ }
                }
            }

            sb.appendLine("    val ${propertyName}: ${getConvertTypeName(it.type)}${if (it.isNullable) "?" else ""}")
        }
        sb.appendLine(")")
    }
    return sb.toString()
}

fun Convert.getConvertTypeName(type: ConvertType): String {
    return when (type) {
        is ConvertClass -> {
            map[type] ?: throw IllegalArgumentException()
        }
        is ArrayType -> {
            "List<${getConvertTypeName(type.type)}>"
        }
        is StringType -> {
            "String"
        }
        is BooleanType -> {
            "Boolean"
        }
        is LongType -> {
            "Long"
        }
        is DoubleType -> {
            "Double"
        }
        is AnyType -> {
            "Any"
        }
    }
}